/**
 * 
 */
package fr.utbm.gi.vi51.slizers.pw.agent;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;

import javax.vecmath.Point2i;

import org.janusproject.kernel.status.Status;
import org.janusproject.kernel.status.StatusFactory;

import fr.utbm.gi.vi51.slizers.framework.agent.Direction;
import fr.utbm.gi.vi51.slizers.framework.agent.SituatedAgent;
import fr.utbm.gi.vi51.slizers.framework.environment.Environment;
import fr.utbm.gi.vi51.slizers.framework.environment.Perception;
import fr.utbm.gi.vi51.slizers.framework.environment.PerceptionPosition;
import fr.utbm.gi.vi51.slizers.framework.learning.QActionResult;
import fr.utbm.gi.vi51.slizers.framework.learning.QValueStore;
import fr.utbm.gi.vi51.slizers.pw.agent.action.CreatureAction;
import fr.utbm.gi.vi51.slizers.pw.agent.action.CreatureActionInfluence;
import fr.utbm.gi.vi51.slizers.pw.environment.WorldElement;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureQAction;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureQLearner;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureQProblem;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureQState;
import fr.utbm.gi.vi51.slizers.pw.learning.CreatureState;

/**
 * 
 * 
 * @author Tarik Belabbas
 * 
 */
public class Creature extends SituatedAgent<CreatureBody> {
	/**
	 * Version number associated to the serializable class.
	 */
	private static final long serialVersionUID = 1818593423114637413L;

	/**
	 * The description of the learning problem.
	 */
	private final CreatureQProblem problem;
	
	/**
	 * The learner.
	 */
	private final CreatureQLearner learner;

	/**
	 * The last state of the creature.
	 */
	private CreatureQState lastState = null;
	
	/**
	 * The last action taken by the creature.
	 */
	private CreatureQAction lastAction = null;

	/**
	 * The position of the exit of the map
	 */
	private final Point2i mapExit;
	
	/**
	 * Default constructor.
	 * @param mapExit Position of the exit of the map
	 */
	public Creature(Point2i _mapExit)
	{
		this.mapExit = _mapExit;
		this.problem = new CreatureQProblem(0.7f, 0.75f, 0.2f, 0.f);
		this.learner = new CreatureQLearner(problem);
	}

	/**
	 * Parameterized constructor.
	 * 
	 * @param _qvaluestore the Q-value store
	 * @param mapExit Position of the exit of the map
	 */
	public Creature( QValueStore< CreatureQState, CreatureQAction > _qvaluestore, Point2i _mapExit )
	{
		this.mapExit = _mapExit;
		this.problem = new CreatureQProblem(0.7f, 0.75f, 0.1f, 0.f);
		
		if ( _qvaluestore != null )
		{
			this.learner = new CreatureQLearner(this.problem, _qvaluestore);
		}
		else
		{
			this.learner = new CreatureQLearner(this.problem);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * fr.utbm.gi.vi51.slizers.framework.environment.SituatedAgent#createBody
	 * (fr.utbm.gi.vi51.slizers.framework.environment.Environment)
	 */
	@Override
	protected CreatureBody createBody( Environment _environment )
	{
		return new CreatureBody( getAddress() );
	}

	protected CreatureQState computeCurrentState( List< Perception > perceptions )
	{
		assert ( perceptions.get( 0 ).getRelativePosition() == PerceptionPosition.DOWN );
		assert ( perceptions.get( 1 ).getRelativePosition() == PerceptionPosition.FRONT );
		assert ( perceptions.get( 2 ).getRelativePosition() == PerceptionPosition.UP );
		assert ( perceptions.get( 3 ).getRelativePosition() == PerceptionPosition.FRONT_DOWN );
		assert ( perceptions.get( 3 ).getRelativePosition() == PerceptionPosition.FRONT_UP );

		WorldElement down = perceptions.get( 0 ).getWorldElement();
		WorldElement front = perceptions.get( 1 ).getWorldElement();
		WorldElement up = perceptions.get( 2 ).getWorldElement();
		WorldElement frontDown = perceptions.get( 3 ).getWorldElement();
		WorldElement frontUp = perceptions.get( 4 ).getWorldElement();

		if ( getBody().getState() == CreatureBodyState.BURNING
				|| getBody().getState() == CreatureBodyState.SINKING
				|| getBody().getState() == CreatureBodyState.GUTS_ON_THE_FLOOR )
		{
			return new CreatureQState( CreatureState.STATE0 );
		}
		else if ( getBody().getState() == CreatureBodyState.EXITTING )
		{
			return new CreatureQState( CreatureState.STATE25 );
		}
		else if ( getBody().getState() == CreatureBodyState.CLIMBING )
		{
			if ( frontUp == WorldElement.FIRE )
			{
				return new CreatureQState( CreatureState.STATE1 );
			}
			else if ( frontUp == WorldElement.AIR )
			{
				return new CreatureQState( CreatureState.STATE2 );
			}
			else
			{
				return new CreatureQState( CreatureState.STATE3 );
			}
		}
		else
		{
			if ( ( down == WorldElement.AIR ) || ( down == WorldElement.WATER )
					|| ( down == WorldElement.FIRE )
					|| ( down == WorldElement.ENTRANCE )
					|| ( down == WorldElement.EXIT ) )
			{
				if ( getBody().getState() == CreatureBodyState.GLIDING )
				{
					return new CreatureQState( CreatureState.STATE4 );
				}
				else
				{
					return new CreatureQState( CreatureState.STATE5 );
				}
			}
			else
			{
				if ( front == WorldElement.EXIT )
					return new CreatureQState( CreatureState.STATE6 );
				else
				{
					if ( down == WorldElement.EARTH )
					{
						if ( front == WorldElement.FIRE )
						{
							return new CreatureQState( CreatureState.STATE7 );
						}
						else
						{
							if ( front == WorldElement.EARTH )
							{
								if ( up == WorldElement.AIR )
									return new CreatureQState(
											CreatureState.STATE8 );
								else
									return new CreatureQState(
											CreatureState.STATE9 );
							}
							else
							{
								if ( front == WorldElement.ROCK )
								{
									if ( up == WorldElement.AIR )
										return new CreatureQState(
												CreatureState.STATE10 );
									else
										return new CreatureQState(
												CreatureState.STATE11 );
								}
								else
								{
									if ( ( frontDown == WorldElement.EARTH )
											|| ( frontDown == WorldElement.ROCK ) )
										return new CreatureQState(
												CreatureState.STATE12 );
									else
									{
										if ( ( frontDown == WorldElement.WATER ) )
											return new CreatureQState(
													CreatureState.STATE13 );
										else
										{
											if ( ( frontDown == WorldElement.FIRE ) )
												return new CreatureQState(
														CreatureState.STATE14 );
											else
												return new CreatureQState(
														CreatureState.STATE15 );
										}
									}
								}
							}
						}
					}
					else
					{
						if ( front == WorldElement.FIRE )
						{
							return new CreatureQState( CreatureState.STATE16 );
						}
						else
						{
							if ( front == WorldElement.EARTH )
							{
								if ( up == WorldElement.AIR )
									return new CreatureQState(
											CreatureState.STATE17 );
								else
									return new CreatureQState(
											CreatureState.STATE18 );
							}
							else
							{
								if ( front == WorldElement.ROCK )
								{
									if ( up == WorldElement.AIR )
										return new CreatureQState(
												CreatureState.STATE19 );
									else
										return new CreatureQState(
												CreatureState.STATE20 );
								}
								else
								{
									if ( ( frontDown == WorldElement.EARTH )
											|| ( frontDown == WorldElement.ROCK ) )
										return new CreatureQState(
												CreatureState.STATE21 );
									else
									{
										if ( ( frontDown == WorldElement.WATER ) )
											return new CreatureQState(
													CreatureState.STATE22 );
										else
										{
											if ( ( frontDown == WorldElement.FIRE ) )
												return new CreatureQState(
														CreatureState.STATE23 );
											else
												return new CreatureQState(
														CreatureState.STATE24 );
										}
									}
								}
							}
						}
					}
				}

			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see fr.utbm.gi.vi51.slizers.framework.environment.SituatedAgent#live()
	 */
	@Override
	public Status live()
	{
		if ( getBody().getState() != CreatureBodyState.OUT_OF_WORLD )
		{
			// Compute perceptions and current state
			List< Perception > perceptions = getBody().getPerceivedObjects();
			CreatureQState currentState = computeCurrentState( perceptions );
	
			// Sets the current state in the CreatureQProblem and choose the
			// next action to take
			problem.setCurrentState( currentState );
	
			if ( lastState != null && lastAction != null )
			{
				float reward = learner.computeReward( lastState, lastAction );
				QActionResult< CreatureQState, CreatureQAction > result = new QActionResult< CreatureQState, CreatureQAction >(
						problem.getCurrentState(), reward );
				learner.learn( lastState, lastAction, result );
			}
	
			CreatureQAction currentAction = learner.chooseAction();
			
			Map<Float,CreatureQAction> map = learner.getBestActions(currentState);
			List<CreatureQAction> list = new ArrayList(map.values());
			int i = 0;
	
			// If the best action brings the creature away from the exit, 
			// our Creature can chose the next one with a probability of 0.8
	
			Random random = new Random();
			
			if(random.nextFloat() < problem.getRho())
				currentState = problem.getRandomState();
			
			else
			{	
				while ( (isMovingAwayFromTheExit( currentAction = list.get(i)) )
						&& (random.nextFloat() < 0.8f) )
				{
					++i;
				}
			}
			
			
	
			// Transform this action into an influence and sends it to the
			// influence collector
			CreatureActionInfluence influence = new CreatureActionInfluence(
					getBody().getOwner(),
					CreatureAction.values()[ currentAction.toInt() ] );
			influenceBody( influence );
	
			this.lastState = currentState;
			this.lastAction = currentAction;
		}

		// Everything went OK (or I hope so)
		return StatusFactory.ok( this );
	}
	
	private CreatureQAction getRandomAction() {
		CreatureQAction currentAction = null;
		Random random = new Random();
		if(random.nextFloat() < 0.5f)
		{
			boolean newActionSelected = false;
			while(!newActionSelected)
			{
				int actionNumber = random.nextInt();
				currentAction = new CreatureQAction(CreatureAction.values()[actionNumber%(CreatureAction.values().length)]);
			}
		}
		return currentAction;
	}
	
	/**
	 * 
	 * @param _action Action taken
	 * @return True if the action takes the creature away from the exit 
	 */
	private boolean isMovingAwayFromTheExit(CreatureQAction _action)
	{
		if(mapExit.x - getBody().getX() < 0) 						// Exit on the left
		{
			if(getBody().getDirection() == Direction.RIGHT)			// Creature facing the right direction
			{
				if((_action.value == CreatureAction.CLIMB_FORWARD)
				 ||(_action.value == CreatureAction.MOVE_FORWARD)
				 ||(_action.value == CreatureAction.DIG_AND_MOVE_FORWARD))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			else													// Creature facing the left direction
			{
				if(_action.value == CreatureAction.TURN_AROUND)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}
		else														// Exit on the right
		{
			if(getBody().getDirection() == Direction.LEFT)			// Creature facing the left direction
			{
				if((_action.value == CreatureAction.CLIMB_FORWARD)
				 ||(_action.value == CreatureAction.MOVE_FORWARD)
				 ||(_action.value == CreatureAction.DIG_AND_MOVE_FORWARD))
				{
					return true;
				}
				else
				{
					return false;
				}
			}
			else													// Creature facing the right direction
			{
				if(_action.value == CreatureAction.TURN_AROUND)
				{
					return true;
				}
				else
				{
					return false;
				}
			}
		}
	}

	/**
	 * Returns the creature learner's QValueStore
	 * 
	 * @return the creature learner's QValueStore
	 */
	public QValueStore< CreatureQState, CreatureQAction > getQValueStore()
	{
		return this.learner.getQValueStore();
	}
}
